7.3.4 Relaciones e Integridad de Datos
Una base de datos no es solo un almacén de tablas aisladas. Su verdadero poder reside en la capacidad de relacionar información y asegurar que esos datos sean coherentes y fiables. Esto se conoce como Integridad Referencial.
En este punto, aprenderemos a conectar tablas y a poner "reglas de juego" para que la base de datos rechace automáticamente información incoherente.
1️⃣ Relaciones entre Tablas: Claves y Conexiones
Para conectar dos tablas, usamos dos conceptos fundamentales:
🟩 Clave Primaria (Primary Key - PK)
Es el "DNI" de cada fila. Identifica de forma única e inequívoca a un registro.
- No puede repetirse.
- No puede ser nula.
🟦 Clave Foránea (Foreign Key - FK)
Es una columna en una tabla (hija) que apunta a la Clave Primaria de otra tabla (padre). Es el "enlace" que crea la relación.
-- Tabla Padre: Autores
CREATE TABLE autores (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT NOT NULL UNIQUE
);
-- Tabla Hija: Libros (cada libro pertenece a un autor)
CREATE TABLE libros (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titulo TEXT NOT NULL,
autor_id INTEGER,
FOREIGN KEY (autor_id) REFERENCES autores(id)
);
Por defecto, SQLite no valida las claves foráneas por compatibilidad histórica. Debes activarlas manualmente al conectar:
conexion = sqlite3.connect("mi_base.db")
conexion.execute("PRAGMA foreign_keys = ON") # Imprescindible
2️⃣ Restricciones de Integridad
Las restricciones son reglas que definimos en las columnas para evitar que entren datos "sucios" o incorrectos.
🟩 UNIQUE y NOT NULL
NOT NULL: Obliga a que el campo tenga un valor.UNIQUE: Impide que dos filas tengan el mismo valor en esa columna (ej: un email o un ISBN).
🟦 CHECK y DEFAULT
CHECK (condición): Solo permite insertar datos que cumplan una regla lógica.DEFAULT valor: Si no se indica nada, pone un valor por defecto.
CREATE TABLE usuarios (
id INTEGER PRIMARY KEY,
nombre TEXT NOT NULL,
email TEXT UNIQUE,
edad INTEGER CHECK(edad >= 18),
rol TEXT DEFAULT 'lector'
);
3️⃣ Acciones Referenciales: ¿Qué pasa si borro al "padre"?
Cuando borras un registro que tiene "hijos" (datos que apuntan a él), ¿qué debe hacer la base de datos?
🟩 ON DELETE RESTRICT (Por defecto)
Impide borrar al padre si tiene hijos. Primero debes borrar los hijos o reasignarlos. Es la opción más segura.
🟦 ON DELETE CASCADE
Si borras al padre, borra automáticamente todos sus hijos. Útil para "limpiezas automáticas" pero peligroso si no se tiene cuidado.
🟨 ON DELETE SET NULL
Si borras al padre, los hijos se quedan, pero su referencia (FK) pasa a ser NULL.
CREATE TABLE libros (
...
FOREIGN KEY (autor_id) REFERENCES autores(id)
ON DELETE CASCADE -- Si borro un autor, borro todos sus libros
);
4️⃣ Manejo de Errores con IntegrityError
Cuando intentamos romper una regla (insertar un duplicado en un campo UNIQUE o apuntar a un ID que no existe), Python lanzará una excepción específica.
import sqlite3
try:
with sqlite3.connect("biblioteca.db") as conn:
conn.execute("PRAGMA foreign_keys = ON")
# Intento de insertar un libro con un autor_id que NO existe
conn.execute("INSERT INTO libros (titulo, autor_id) VALUES (?, ?)", ("Inexistente", 999))
except sqlite3.IntegrityError as e:
print(f"❌ Error de integridad detectado: {e}")
✅ Buenas prácticas en Diseño Relacional
- Normaliza: Si ves que repites mucho un texto (ej: nombres de autores), sácalo a una tabla propia y usa IDs. Ahorra espacio y evita errores de escritura.
- Activa siempre el PRAGMA: No confíes en que SQLite lo haga solo. Hazlo nada más conectar.
- Usa Nombres Claros: Llama a tus claves foráneas con el formato
tablaPadre_id(ej:autor_id) para que sea fácil seguir el rastro. - Defensa en la Base: Es mejor que la base de datos lance un error (
CHECK,UNIQUE) a validar todo mil veces en Python. Delega la seguridad en el esquema.
🧪 Ejercicios prácticos – Misión: La Cripta de Datos (Parte 4)
La Cripta de Datos está creciendo tanto que el sistema actual es un caos. El bibliotecario jefe quiere normalizar la base de datos: separar los libros de sus autores y de sus categorías para evitar duplicados.
🟢 Fase 1 – Reestructuración Masiva
🟩 Ejercicio 1 – El Archivo de Sabios
Crea una tabla autores (con id PK y nombre UNIQUE). Crea también una tabla categorias (con id PK y nombre UNIQUE).
🟦 Ejercicio 2 – Libros Conectados
Modifica el esquema de la tabla libros (puedes borrarla y crearla de nuevo) para que no guarde el nombre del autor ni la categoría como texto, sino como autor_id y categoria_id. Define las Foreign Keys correspondientes y activa el PRAGMA foreign_keys = ON.
🟡 Fase 2 – Blindaje de Datos
🟨 Ejercicio 3 – Reglas de la Orden
Añade las siguientes restricciones a la tabla libros:
- El
titulodebe ser único (UNIQUE). - El número de
paginasdebe ser siempre superior a 0 (CHECK). - El
autor_idno puede ser nulo (NOT NULL).
🟧 Ejercicio 4 – El Detector de Errores
Crea un script que intente realizar las siguientes acciones ilegales y capture el IntegrityError mostrando un mensaje personalizado para cada uno:
- Insertar un autor que ya existe.
- Insertar un libro con 0 páginas.
- Insertar un libro apuntando a un
autor_idque no existe.
🔴 Fase 3 – Gestión de Despedidas
🟥 Ejercicio 5 – Borrado en Cascada
Configura la relación para que, si un autor es expulsado de la biblioteca secreta y su registro es borrado, todos sus libros desaparezcan automáticamente con él (ON DELETE CASCADE). Prueba el funcionamiento insertando un autor, varios libros suyos y luego borrando solo al autor.
🟪 Ejercicio 6 – La Gran Consulta (Preview)
Realiza una consulta que muestre por pantalla: "El libro [Título] fue escrito por el autor [Nombre]".
(Pista: necesitarás unir ambas tablas en tu SELECT comparando libros.autor_id == autores.id)